/*
This source file is part of the open source TripleGasm project
Copyright (C) 2010 Roma Kovalev

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

For the latest info, see http://code.google.com/p/triplegasm
You can contact the author(s) via e-mail: endiannesz@gmail.com
*/

#include "manipulator.hpp"
#include "ball.hpp"

using namespace Ogre;
using namespace TripleGasm;
using namespace std;

Manipulator::Manipulator(Ogre::SceneManager* pSm) : mMoveSpeed(0)
{
  mpNode= pSm->getRootSceneNode()->createChildSceneNode(Vector3::ZERO);
  // Don't do this here! this pointer is unstable, do this after construction.
  // mpNode->getUserObjectBindings().setUserAny(static_cast<Any>(this));

  mpAttachPoints[LEFT]	= mpNode->createChildSceneNode(LEFT_POINT);
  mpAttachPoints[RIGHT]	= mpNode->createChildSceneNode(RIGHT_POINT);
  mpAttachPoints[TOP]		= mpNode->createChildSceneNode(TOP_POINT);
  mpAttachPoints[BOTTOM]= mpNode->createChildSceneNode(BOTTOM_POINT);

  // The manipulators position is in the center of a square matrix
  mGraphPos= make_pair(MAX_SPHERES, MAX_SPHERES);
  for (BYTE i= 0; i < MATRIX_SIZE; ++i)
    for (BYTE j= 0; j < MATRIX_SIZE; ++j)
      mLocalMatrix[i][j]= nullptr;

    // All 3 will rotate in different directions
    mpOuterNode = pSm->getRootSceneNode()->createChildSceneNode(Vector3::ZERO);
    mpCenterNode= mpOuterNode ->createChildSceneNode(Vector3::ZERO);
    mpInnerNode = mpCenterNode->createChildSceneNode(Vector3::ZERO);

    mpOuterEnt = pSm->createEntity("ManipOuter" , "3.mesh");
    mpCenterEnt= pSm->createEntity("ManipCenter", "2.mesh");
    mpEnt      = pSm->createEntity("ManipInner" , "1.mesh");

    mpOuterNode ->attachObject(mpOuterEnt);
    mpInnerNode ->attachObject(mpEnt);
    mpCenterNode->attachObject(mpCenterEnt);

    mpOuterNode ->setScale(Real(0.3), Real(0.3), Real(0.3));

#ifdef _DEBUG
    mpEnt->setQueryFlags(SPHERE);
    mpCenterEnt->setQueryFlags(SPHERE);
    mpOuterEnt->setQueryFlags(SPHERE);
#endif
}

Manipulator::Manipulator() : mMoveSpeed(0)
{
  // The manipulators position is in the center of a square matrix
  mGraphPos= make_pair(MAX_SPHERES, MAX_SPHERES);
  for (BYTE i= 0; i < MATRIX_SIZE; ++i)
    for (BYTE j= 0; j < MATRIX_SIZE; ++j)
      mLocalMatrix[i][j]= nullptr;
}

Manipulator::~Manipulator()
{

}

bool Manipulator::AddSphere(Ball* pSphere)
{
  // Check if a sphere intersects with the manipulator.
  bool isAttached= false;
  if (isAttached= pSphere->IsIntersection(this))
    pSphere->AttachTo(this);
  else if (mSpheres.size())
  {
    for (auto i= mSpheres.begin(); i != mSpheres.end() && !isAttached; ++i)
      if ( isAttached= pSphere->IsIntersection((*i)) )
        pSphere->AttachTo( (*i) );
  }

  if (isAttached)
  {
    mSpheres.push_back(pSphere);
    SetLocalMatrixElement(pSphere);
    pSphere->CorrectSphereAttachPoints(mLocalMatrix);
    TryDeleteSpheres();
    return true;
  }

  return false;
}

void Manipulator::TryDeleteSpheres()
{
  Ball* pLastSphere= mSpheres.back();

  // If there are less than 3 spheres, then there cannot be a triple-pop..
  if (mSpheres.size() >= 3)
  {
    auto result= pLastSphere->IsTriplePop();
    if (result.size() >= 3)
    {
      for_each(result.begin(), result.end(), [&](Ball* pSphere)
      {
        ClearLocalMatrixElement(pSphere);
        pSphere->Destroy();
      });

      // Don't put this code in the above lambda function, because
      // the UserObjectBindings should be reset -after- the entities are
      // destroyed.
      for_each(result.begin(), result.end(), [&](Ball* pSphere)
      {
        pSphere->SetNodeData(nullptr);
        mSpheres.remove(pSphere);
        delete pSphere;
      });
#if 0
      // Since some spheres are not in the scene anymore, other spheres
      // might be floating around, unattached to anything.
      // The spheres must be found and removed.
      for (auto it= mSpheres.begin(); it != mSpheres.end(); ++it)
      {

        auto spheres= (*it)->IsIsolatedSphere();
        if (spheres.size())
        {
          for_each(spheres.begin(), spheres.end(), [&](Ball* pSphere)
          {
            ClearLocalMatrixElement(pSphere);
            pSphere->Destroy();
          });

          for_each(spheres.begin(), spheres.end(), [&](Ball* pSphere)
          {
            pSphere->SetNodeData(nullptr);
            mSpheres.remove(pSphere);
            delete pSphere;
          });
          break;
        }
      }
#endif
    } // If triple-pop
    else // No triple pop
    {
      for_each(result.begin(), result.end(), [&](Ball* pSphere)
      {pSphere->isChecked= false;});
    }
  } // If >= 3 spheres attached
}

void Manipulator::Move(const Ogre::FrameEvent& EVT)
{
  if (mMoveSpeed)
    mpNode->roll(Degree(mMoveSpeed * EVT.timeSinceLastFrame));

  Real rotateAmount= 10 * EVT.timeSinceLastFrame;
  mpInnerNode ->yaw  (Degree(rotateAmount));
  mpCenterNode->pitch(Degree(rotateAmount));
  mpOuterNode ->yaw  (Degree(rotateAmount));
}

void Manipulator::SetSpeed(const Real SPEED)
{
  mMoveSpeed= SPEED;
}

const Ogre::Real Manipulator::MANIPULATOR_SPEED= 80;

const Ogre::Vector3 Manipulator::GetPosition() const
{
  return Vector3(0, 0, 0);
}

const Ogre::Vector3 Manipulator::GetDerivedPosition() const
{
  return Vector3(0, 0, 0);
}

void Manipulator::SetLocalMatrixElement(BaseSphere* pSphere)
{
  auto pos= pSphere->GetGraphPos();
  mLocalMatrix[pos.first][pos.second]= pSphere;
}

BaseSphere* Manipulator::GetLocalMatrixElement(const std::pair<BYTE, BYTE>& POS,
  const direction DIRECTION)
{
  switch (DIRECTION)
  {
  case NONE:
    return mLocalMatrix[POS.first][POS.second];
    break;
  case LEFT:
    return mLocalMatrix[POS.first - 1][POS.second];
    break;
  case RIGHT:
    return mLocalMatrix[POS.first + 1][POS.second];
    break;
  case TOP:
    return mLocalMatrix[POS.first][POS.second - 1];
    break;
  case BOTTOM:
    return mLocalMatrix[POS.first][POS.second + 1];
    break;
  default:
    throw logic_error("An invalid direction was supplied to"
      "TripleGasm::Manipulator::GetGraphTableElement");
  }
}

void Manipulator::ClearLocalMatrixElement(BaseSphere* pSphere)
{
  auto pos= pSphere->GetGraphPos();
  mLocalMatrix[pos.first][pos.second]= nullptr;
}

void Manipulator::ClearLocalMatrixElement(Ball* pSphere)
{
  auto pos= pSphere->GetGraphPos();
  mLocalMatrix[pos.first][pos.second]= nullptr;
}

void Manipulator::SetNodeData(BaseSphere* pSphere)
{
  BaseSphere::SetNodeData(pSphere);

  mpOuterNode ->getUserObjectBindings().setUserAny(static_cast<Any>(static_cast<BaseSphere*>(this)));
  mpInnerNode ->getUserObjectBindings().setUserAny(static_cast<Any>(static_cast<BaseSphere*>(this)));
  mpCenterNode->getUserObjectBindings().setUserAny(static_cast<Any>(static_cast<BaseSphere*>(this)));
}
